How to Create Chatter Post in lightning web component

Jan 18, 2024


 

 

 

Let's jump into the code,

We need to create below component:

  1. postComponent
  2. sharedPosted
  3. writeComment

 

Here we will create chatter post component to practice the lightning web components. Here we will learn how can established communication between components. And also we will use image from static resource. First we create "postComponent" which is parent component. This component show the form to write post. In this component we have called child component which is named sharedPosted. To call child component we have to written like <c-shared-posted></c-shared-posted> in postComponent html file

postComponent.html

<template>
 
    <lightning-tabset variant="scoped">
        <lightning-tab label="Post">
            <lightning-input-rich-text placeholder="Share an update..." value={richtextvalue} onchange={richTextOnChangehandler} formats={formats}></lightning-input-rich-text>
            <div class="slds-text-body_small" style="text-align-last:end;">To link to a record, enter / then start typing the record name.</div>
            <div class="slds-text-body_small"><strong>To</strong> My Followers</div>
            <lightning-button-icon icon-name="utility:attach" variant="container" alternative-text="attach" title="Attach" ></lightning-button-icon>
           
            <div style="text-align-last:end;">
                <lightning-button class="slds-align_absolute-right" label="Share" variant="brand" disabled={shareDisable} onclick={sharedhandler}></lightning-button>
            </div>
        </lightning-tab>
    </lightning-tabset>
   
    <!-- Add component -->
    <c-shared-posted></c-shared-posted>
   
   
</template>

 

Here is a postComponent.js file. In this file we have create sharedhandler event which will do invoke the method(sharedPost()) of child component (sharedPosted). And We shared the value of <lightning-input-rich-text> through a parameter from postComponent to sharedPosted child component.

postComponent.js
 
import { LightningElement, api } from 'lwc';
import { NavigationMixin } from "lightning/navigation";

export default class PostComponent extends NavigationMixin(LightningElement) {
    richtextvalue;
    shareDisable=true;
    formats = [
        'bold',
        'italic',
        'underline',
        'strike',
        'clean',
        'list',
        'image',
        'link',
        'mention',

    ];

   
    richTextOnChangehandler(event) {
        this.richtextvalue = event.target.value;
        this.richtextvalue.length > 0 ? this.shareDisable=false : this.shareDisable=true;
    }

    sharedhandler(event) {
        this.template.querySelector('c-shared-posted').sharedPost(this.richtextvalue)
        let richInput = this.template.querySelector('lightning-input-rich-text');
        richInput.value = '';
    }

}

 

Here is a postComponent.js-meta.xml file which is used for set configuration that on which page we want to show the our component.

postComponent.js-meta.xml
 
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>58.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

 

Here is child component which is named "sharedPosted". This component will show when we shared post. In this component we display the picklist to sort the shared post, and we can search the feed. And also we have added the another child component <c-write-comment>.

sharedPosted.html
 
<template>
    <template lwc:if={show}>
        <lightning-card class="slds-m-top_large slds-p-horizontal_large">
       
            <lightning-layout horizontal-align="spread" class="slds-p-horizontal_large">
                <lightning-layout-item  padding="around-small">
                    <lightning-combobox label="Sort by" value={value} options={options} onchange={optionChangeHandler}></lightning-combobox>
                </lightning-layout-item>
                <lightning-layout-item  padding="around-small">
                    <div style="display: flex;">
                        <lightning-input name="enter-search" placeholder="Search this feed..." type="search" value={searchfeed} onkeypress={searchChangeHandler}></lightning-input>
                        <lightning-button-icon class="slds-m-left_x-small" icon-name="utility:refresh" alternative-text="Refresh this feed" title="Refresh" onclick={refreshFeedsHandler} style="margin-top:19px;"></lightning-button-icon>
                    </div>
                </lightning-layout-item>
            </lightning-layout>
           
            <template for:each={postData} for:item="post" >
                <div key={post.id}>
                    <c-write-comment onaddcomment={insertcomment} post={post}  onedit={editHandler} ondelete={deleteHandler} onbookmark={bookmarkHandler}></c-write-comment>
                </div>
            </template>
           
        </lightning-card>
    </template>
</template>
 
sharedPosted.css
 
.lgc-bg {
    background-color: rgb(242 242 242);
}

 

In this component "optionChangeHandler" use for to sort the post by 'Top post' and 'Latest post'. which is calling filterByOptions function. This function accept the value of optionChangeHandler event. Which is doing filter the post according the options.

This "searchChangeHandler" event fire when we search the post feed. After enter value of search and hit enter it will do call searchList method which is accepting three parameter one for post data, second is query means what do you want search and third is Field means On which field you want to search.

sharedPosted.js
 
import { LightningElement, api, track } from 'lwc';

export default class SharedPosted extends LightningElement {
   
    value = '';
    show = false;
    @track postData = [];
    items = [];
    commentCount = 0;
    id = 0;
   
    get options() {
        return [
            { label: 'Top Posts', value: 'toppost' },
            { label: 'Latest Posts', value: 'latestpost' },
            { label: 'Most Recent Activity', value: 'mostrecenty_activity' },
        ];
    }
   
    optionChangeHandler(event) {
        this.value = event.target.value;
        this.filterByOptions(this.value);
    }

    searchChangeHandler(event) {
        let query = event.target.value;
        let keycode = event.which;
        if (keycode == 13) {
            console.log(`query: ${query}`)
            const fieldToSearch = ["post"];
            this.searchList(this.postData, query, fieldToSearch);
        }
        let crossbtn = this.template.querySelector(`[data-element-id="searchClear"]`);
        console.log(crossbtn);
    }
   

    refreshFeedsHandler(event) {
        this.postData = JSON.parse(JSON.stringify(this.items));
    }

    @api
    sharedPost(richTextValue) {
        this.id = this.id + 1;
        this.id > 0 ? this.show = true : this.show = false;
        const timestamp = this.getDateTime();
        this.insertdata(this.id, richTextValue, timestamp);
    }


    editHandler(event) {
        let id = event.detail.postid;
    }

    deleteHandler(event) {
        let id = event.detail.postid;
        this.postData = this.items.filter((element) => {
            return element.id !== parseInt(id);
        });
        if (this.postData.length === 0)
            this.show = false

        this.items = this.postData;
    }

    bookmarkHandler(event) {
        let id = event.detail.postid;
    }

   
    // Add post data
    insertdata(id, richTextValue, timestamp) {
        this.items.unshift({ id: id, post: richTextValue, timestamp: timestamp, comments: [] })        
        this.postData = JSON.parse(JSON.stringify(this.items));
    }
   
    // Add comment to individualy post
    insertcomment(event) {
        let id = event.detail.id;
        let comment = event.detail.comment;

        this.items.forEach((ele, index) => {
            const timestamp = this.getDateTime();
            if (id == ele.id)
                ele.comments.push({ timestamp: timestamp, comment: comment });
        })
        this.items = this.items.map((ele, index) => ({ ...ele, count: ele.comments.length}));
       
        this.postData = this.items;
    }

    // filter by options
    filterByOptions(options) {
        console.log('filter by', options);
        if (options == 'toppost') {
            this.postData = this.items;
            var i, j, temp, swapped;
            let n = this.items.length;
            for (i = 0; i < n - 1; i++) {
                swapped = false;
                for (j = 0; j < n - i - 1; j++) {
                    if (this.postData[j].count < this.postData[j + 1].count) {
                        temp = this.postData[j];
                        this.postData[j] = this.postData[j + 1];
                        this.postData[j + 1] = temp;
                        swapped = true;
                    }
                }
                if (swapped == false)
                    break;

            }
        }
        if (options == 'latestpost') {
            var startDate = new Date().toJSON().slice(0, 10);
            this.postData = this.items.filter((item) => {
                return item.timestamp.toJSON().slice(0, 10) == startDate;
            });
        }

    }

    // Search feed
    searchList(data, query, fieldToSearch) {
        const result = [];
        function search(obj) {
            for (const key in obj) {
                const value = obj[key];
                if (fieldToSearch.includes(key) && value.toString().toLowerCase().includes(query.toLowerCase())) {
                    result.unshift(obj);
                    break;
                }
                if (typeof value == 'object') {
                    search(value);
                } else if (Array.isArray(value)) {
                    value.forEach(search);
                }
            }
        }
        search(data);
        // return result;
        this.postData = result.reverse();
    }

    // Get today date time
    getDateTime() {
        let todays = new Date();
        return todays;

    }
}
 
sharedPosted.js-meta.xml
 
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>58.0</apiVersion>
    <isExposed>false</isExposed>
</LightningComponentBundle>

 

Here is writeComment component. which is showing the post and related post comments. In this component we can add comment on particular post. And also we will learn how we can send data of child component to parent component. To send data to parent component (Here sharedPosted component is parent component for writeComment component) we have to create custom event which will fire insertcomment event in sharedPosted component.

writeComments.html
 
<template>
    <lightning-layout horizontal-align="spread" class="slds-p-horizontal_large">
       
        <lightning-layout-item padding="around-small">
            <div style="display:flex">
                <lightning-avatar variant="circle" src={profile_avtaar} initials="AW" fallback-icon-name="standard:person_account" alternative-text="Amy Weaver" class="slds-m-right_small"></lightning-avatar>
                <div class="slds-text-body_small">
                    <a href="#" title="Jason Rodgers">Test — test</a>  
                    <template lwc:if={showTime}>
                        <p class="slds-text-link_reset">{showTime}</p>
                    </template>
                    <template lwc:else>
                        <p class="slds-text-link_reset">Just now</p>
                    </template>
                </div>
            </div>
        </lightning-layout-item>
   
       
        <lightning-layout-item padding="around-small" >
            <lightning-button-menu alternative-text="Show menu">
                <lightning-menu-item label="Edit" value={post.id} onclick={onEditHandler}></lightning-menu-item>
                <lightning-menu-item label="Delete" value={post.id} onclick={onDeleteHandler}></lightning-menu-item>
                <lightning-menu-item label="Bookmark" value={post.id} onclick={onBookmarkHandler}></lightning-menu-item>
            </lightning-button-menu>
        </lightning-layout-item>
    </lightning-layout>
   
    <lightning-layout class="slds-p-horizontal_large">
        <lightning-layout-item>
            <div class="slds-text-body_small slds-p-horizontal_large">
                <lightning-formatted-rich-text value={post.post}></lightning-formatted-rich-text>
            </div>
        </lightning-layout-item>
    </lightning-layout>

    <lightning-layout class="slds-p-horizontal_large">
        <lightning-layout-item>
            <div class="slds-p-around_medium" style="display:flex;">
                <button title="Like this item" class="slds-button_reset slds-text-body_small slds-post__footer-action" onclick={likedHandler}>
                    <lightning-icon icon-name='utility:like' alternative-text='like' size='x-small' title='like'></lightning-icon>Like
                </button>
                   
                <button title="Comment on this item" class="slds-button_reset slds-text-body_small slds-post__footer-action" onclick={commentHandler}>
                    <lightning-icon icon-name='utility:comments' alternative-text='comments' size='x-small' title='comments'></lightning-icon> Comment
                </button>
               
                <button title="Share this item" class="slds-button_reset slds-text-body_small slds-post__footer-action" onclick={shareHandler}>
                    <lightning-icon icon-name='utility:share' alternative-text='share' size='x-small' title='share'></lightning-icon> Share
                </button>
            </div>
        </lightning-layout-item>

        <lightning-layout-item alignment-bump="left">
            <template lwc:if={post.count}>
                <div class="slds-p-around_medium slds-text-body_small">{post.count} comments</div>
            </template>
        </lightning-layout-item>
    </lightning-layout>
   
    <template lwc:if={showComment}>
        <template for:each={post.comments} for:item="comment">
            <div key={post.id}>
                <lightning-layout multiple-rows class="slds-p-horizontal_large" >
       
                    <lightning-layout-item size="12" padding="around-small" style="background-color:rgb(243 243 243);">
                        <div style="display:flex">
                            <lightning-avatar variant="circle" src={profile_avtaar} initials="AW" fallback-icon-name="standard:person_account" alternative-text="Amy Weaver" class="slds-m-right_small"></lightning-avatar>
                            <div class="slds-text-body_small">
                                <a href="#" title="Jason Rodgers">Test — test</a>  
                                <!-- <p class="slds-text-link_reset">Just now</p> -->
                            </div>
                        </div>
                    </lightning-layout-item>
                    <lightning-layout-item size="12" padding="around_small" style="background-color:rgb(243 243 243);">
                        <div class="slds-text-body_small slds-p-bottom_small slds-m-right_small" style="margin-left:56px;">
                            <lightning-formatted-rich-text value={comment.comment}></lightning-formatted-rich-text>
                        </div>
                    </lightning-layout-item>
                    <lightning-layout-item size="12" padding="around_small" style="background-color:rgb(243 243 243);">
                        <div class="slds-text-body_small slds-p-bottom_small" style="margin-left:56px;">
                            <button title="Like this item" class="slds-button_reset slds-text-body_small slds-post__footer-action" aria-pressed="false">Like</button>
                        </div>
                    </lightning-layout-item>
                </lightning-layout>
            </div>
        </template>
    </template>
   

    <lightning-layout class="slds-p-horizontal_large" >
        <lightning-layout-item class="slds-p-top_large" size="1" style="background-color:rgb(243 243 243);">
            <lightning-avatar variant="circle" size="small" src={profile_avtaar} initials="AW" fallback-icon-name="standard:person_account" alternative-text="Amy Weaver" class="slds-m-left_small slds-p-top_small" ></lightning-avatar>
        </lightning-layout-item>
        <lightning-layout-item  class="slds-p-top_small slds-p-bottom_small slds-p-right_small" size="11" style="background-color:rgb(243 243 243);">
            <lightning-input-rich-text data-id="comment-rich-text" class="slds-p-right_small slds-p-top_small" placeholder="Write a comment..." value={comment} formats={formats} onchange={commentChangeHandler} onfocus={commentClickHandler} ></lightning-input-rich-text>
            <template lwc:if={visibleCommentBtn} >
                <div style="text-align-last:end;" class="slds-m-around_small">
                    <lightning-button class="slds-align_absolute-right" value={post.id} label="Comment" variant="brand" disabled={commentDisabled} onclick={commentBtnClickHandler}></lightning-button>
                </div>
            </template>
        </lightning-layout-item>
    </lightning-layout>
</template>

 

Here commentHandler event is use for set focus on comment box of particular post when we click on comment button on every post.

In onDeleteHandler event create custom event "delete". Which will take id of post which would like you delete. This id will send into its parent component (sharedPosted component). Like: <c-write-comment ondelete={deleteHandler}></c-write-comment>

To add comment on particular post we have to create <lightning-input-rich-text> field. To get this field value create "commentChangeHandler" event. After getting the value of comment we passed it through creating custom event which is named "addcomment". This "addcomment" will use in its parent component by prefix "on" like <c-write-comment onaddcomment={insertcomment}></c-write-comment>

writeComment.js
 
import { LightningElement, api} from 'lwc';
import profile_pic from "@salesforce/resourceUrl/Profile_avtaar";

export default class WriteComment extends LightningElement {
    @api post;
    profile_avtaar = profile_pic;
    commentDisabled=true;
    formats = ['bold','italic', 'underline','strike','clean','list','image','link','mention'];
    showComment=false;
    commentvalue;
    visibleCommentBtn = false;
    showTime;
   
    renderedCallback() {
        console.log('renderedCallback.....')
        setInterval(()=>{
            let time, currentTime, postTime;
            postTime = new Date(this.post.timestamp);
            currentTime = new Date()
            time = this.formatTimeDifference(currentTime, postTime);
            console.log('time:'+ time);
            this.showTime = time;
        }, 60000);
    }
       
    onEditHandler(event) {
        let id = event.target.value;
        const fireEvent = new CustomEvent('edit', {
            detail: {postid: id}
        });
        this.dispatchEvent(fireEvent);
    }

    onDeleteHandler(event) {
        let id = event.target.value;
        const fireEvent = new CustomEvent('delete', {
            detail: {postid: id}
        });
        this.dispatchEvent(fireEvent);
    }

    onBookmarkHandler(event) {
        let id = event.target.value;
        const fireEvent = new CustomEvent('bookmark', {
            detail: {postid: id}
        });
        this.dispatchEvent(fireEvent);
    }


    likedHandler() {

    }

    commentHandler() {
        let commentInput = this.template.querySelector(`[data-id="comment-rich-text"]`);
        commentInput.focus();
    }

    shareHandler() {

    }

    commentClickHandler() {
        this.visibleCommentBtn = true;
    }

    commentChangeHandler(event) {
        this.comment = event.target.value;
        this.comment.length > 0 ? this.commentDisabled=false : this.commentDisabled=true;
    }  

    commentBtnClickHandler(event) {
        this.commentvalue = this.comment;
        this.comment.length > 0 ? this.showComment=true : this.showComment=false;
        let commentInput = this.template.querySelector(`[data-id="comment-rich-text"]`);
        commentInput.value = '';
       
        let commentid = event.target.value;
       
        const fireEvent = new CustomEvent('addcomment', {
            detail:{id: commentid, comment: this.commentvalue}
        })
        this.dispatchEvent(fireEvent)

    }

    formatTimeDifference(end, start) {
        let timeDifference = end - start
        let seconds = Math.floor(timeDifference / 1000);
        let minutes = Math.floor(seconds / 60);
        let hours = Math.floor(minutes / 60);
        let days = Math.floor(hours / 24);
       
        let m = start.toLocaleString('default', {month:'long'});
        let d = start.getDate();
        let y = start.getFullYear();
        let h = start.getHours();
        let min = start.getMinutes();
        let ampm = h >= 12 ? 'PM' : 'AM';
     
        let formattedTime = "";
        if (hours > 1 && hours < 2){
          formattedTime += hours+"h"+min+"m ago";
        }
        if (hours > 0 && hours < 24) {
          formattedTime += hours + "h ago";
        }
        if (minutes > 0 && minutes < 59) {
          formattedTime += minutes % 60 + "m ago";
        }
        // if (minutes == 0) {
        //   formattedTime += "Just now";
        // }
        if (days > 0 && days <=10) {
          formattedTime += days + "d ago";
        }
        if(days > 10) {
          formattedTime += `${m} ${d}, ${y} at ${h}:${min} ${ampm}`
        }
       
        // return formattedTime;
        return formattedTime;
    }
}
 
 
writeComment.js-meta-xml
 
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>58.0</apiVersion>
    <isExposed>false</isExposed>
</LightningComponentBundle>

 

I hope this blog helped you!